﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AsfMojoUI.Model;
using AsfMojoUI.NAudio;
using AsfMojoUI.ViewModel;
using System.Threading.Tasks;
using System.Media;
using NAudio.Wave;
using AsfMojo.Media;
using System.IO;
using AsfMojo.Parsing;
using System.Threading;

namespace AsfMojoUI.View
{
    /// <summary>
    /// Interaction logic for WaveFormControl.xaml
    /// </summary>
    public partial class WaveFormControl : UserControl
    {
        int renderPosition = 0;
        double yTranslate = 40;
        double yScale = 40;

        List<Line> lines = new List<Line>();
        private SampleAggregator aggregator;
        private List<Tuple<float,float>> fftSamples;
        private uint startTimeOffset;

        private const int _fftBucketSize = 150;
        private const int _uiUpdateBucketSize = 5;
        private object fftLockObject = new object();

        public int MaxSampleCount { get; set; }


        public WaveFormControl()
        {
            InitializeComponent();

            fftSamples = new List<Tuple<float, float>>();
            MaxSampleCount = 50000;

            this.SizeChanged += new SizeChangedEventHandler(WaveFormControl_SizeChanged);

            this.DataContextChanged += new DependencyPropertyChangedEventHandler(WaveFormControl_DataContextChanged);

            aggregator = new SampleAggregator();
            aggregator.NotificationCount = _fftBucketSize;
            aggregator.PerformFFT = true;
            aggregator.MaximumCalculated += new EventHandler<MaxSampleEventArgs>(aggregator_MaximumCalculated);
        }

        void WaveFormControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender != null && ((FrameworkElement)sender).DataContext != null)
            {
                Clear();
                PayloadInfo payloadInfo = ((PayloadInfo)(((FrameworkElement)sender).DataContext));
                startTimeOffset = payloadInfo.PresentationTime - AsfHeaderItem.Configuration.AsfPreroll;


                if (payloadInfo != null)
                    Task.Factory.StartNew((Action)(() => { ComputeWaveForm(startTimeOffset); }));
            }
        }

        void WaveFormControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            // Remove everything as we are going to rescale vertically
            renderPosition = 0;
            ClearAllLines();

            this.yTranslate = mainCanvas.ActualHeight / 2;
            this.yScale = mainCanvas.ActualHeight / 2;

            MaxSampleCount = (int)(_fftBucketSize * (ActualWidth+1));
                       
            //trigger re-rendering
            Clear();
            PayloadInfo payloadInfo = ((PayloadInfo)(((FrameworkElement)sender).DataContext));
            startTimeOffset = payloadInfo.PresentationTime - AsfHeaderItem.Configuration.AsfPreroll;


            if (payloadInfo != null)
                Task.Factory.StartNew((Action)(() => { ComputeWaveForm(startTimeOffset); }));
        }

        public void Clear()
        {
            lines.Clear();
            mainCanvas.Children.Clear();
            Reset();
        }


        private void ClearAllLines()
        {
            for (int n = 0; n < lines.Count; n++)
            {
                lines[n].Visibility = Visibility.Collapsed;
            }
        }

        public void AddValue(float maxValue, float minValue)
        {
            int pixelWidth = (int)ActualWidth;
            if (pixelWidth > 0)
            {
                if (renderPosition < ActualWidth)
                    CreateLine(maxValue, minValue);
            }
        }

        private Line CreateLine(float maxValue, float minValue)
        {
            Line line;
            if (renderPosition >= lines.Count)
            {
                line = new Line();
                lines.Add(line);
                mainCanvas.Children.Add(line);
            }
            else
            {
                line = lines[renderPosition];
            }
            line.Stroke = this.Foreground;
            line.X1 = renderPosition;
            line.X2 = renderPosition;
            line.Y1 = yTranslate + minValue * yScale;
            line.Y2 = yTranslate + maxValue * yScale;
            renderPosition++;
            line.Visibility = Visibility.Visible;
            return line;
        }

        /// <summary>
        /// Clears the waveform and repositions on the left
        /// </summary>
        public void Reset()
        {
            renderPosition = 0;
            ClearAllLines();
        }

        public void ComputeWaveForm(UInt32 presentationTime)
        {
            try
            {
                aggregator.Reset();
                double timeInSeconds = presentationTime;
                timeInSeconds /= 1000;

                using (AsfStream asfStream = new AsfStream(AsfStreamType.asfAudio, ViewModelLocator.MainStatic.FileName, timeInSeconds))
                using (AsfAudio asfAudio = new AsfAudio(asfStream))
                {
                    foreach (AudioSample sample in asfAudio.GetSamples(MaxSampleCount))
                    {
                        aggregator.Add(sample.Left);
                    }
                }
            }
            catch (AsfStreamException) { }
        }

        private void aggregator_MaximumCalculated(object sender, MaxSampleEventArgs e)
        {
            lock (fftLockObject)
            {
                fftSamples.Add(new Tuple<float, float>(e.MaxSample, e.MinSample));

                if (fftSamples.Count >= _uiUpdateBucketSize)
                {
                    Tuple<float, float>[] readySamples = new Tuple<float, float>[fftSamples.Count];
                    fftSamples.CopyTo(readySamples);
                    fftSamples.Clear();
                    Dispatcher.BeginInvoke((Action)(() => UpdateUI(readySamples)));
                }
            }
        }

        private void UpdateUI(Tuple<float,float>[] samples)
        {
            foreach (Tuple<float, float> sample in samples)
            {
                AddValue(sample.Item1, sample.Item2);
            }
        }

        private void WaveFormPlayButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                double timeInSeconds = (double)startTimeOffset / 1000;
                using(AsfStream asfStream = new AsfStream(AsfStreamType.asfAudio, ViewModelLocator.MainStatic.FileName, timeInSeconds))
                using (AsfAudio asfAudio = new AsfAudio(asfStream))
                {
                    //play a one second sample
                    byte[] data = asfAudio.GetSampleBytes((int)asfStream.Configuration.AudioSampleRate * asfStream.Configuration.AudioChannels * asfStream.Configuration.AudioBitsPerSample / 8);
                    WaveMemoryStream mwav = new WaveMemoryStream(data, (int)asfStream.Configuration.AudioSampleRate, asfStream.Configuration.AudioBitsPerSample, asfStream.Configuration.AudioChannels);
                    SoundPlayer sp = new SoundPlayer(mwav);
                    sp.Play();
                }
            }
            catch (AsfStreamException) { }
        }



    }
}
